// 版权所有2010 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package textproto

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
	"sync"
)

// 读卡器实现了从文本协议网络连接读取请求或响应的便捷方法。
type Reader struct {
	R   *bufio.Reader
	dot *dotReader
	buf []byte // readContinuedLineSlice的可重用缓冲区
}

// NewReader返回从r读取的新读取器。
// 
// 为避免拒绝服务攻击，提供的bufio。读卡器
// 应该从io读取。LimitReader或类似的阅读器绑定
// 响应的大小。
func NewReader(r *bufio.Reader) *Reader {
	commonHeaderOnce.Do(initCommonHeader)
	return &Reader{R: r}
}

// ReadLine从r中读取一行，
// 从返回的字符串中删除\n或\r\n最后一行。
func (r *Reader) ReadLine() (string, error) {
	line, err := r.readLineSlice()
	return string(line), err
}

// ReadLineBytes类似于ReadLine，但返回的是[]字节而不是字符串。
func (r *Reader) ReadLineBytes() ([]byte, error) {
	line, err := r.readLineSlice()
	if line != nil {
		buf := make([]byte, len(line))
		copy(buf, line)
		line = buf
	}
	return line, err
}

func (r *Reader) readLineSlice() ([]byte, error) {
	r.closeDot()
	var line []byte
	for {
		l, more, err := r.R.ReadLine()
		if err != nil {
			return nil, err
		}
		// 如果第一次通话产生一整行，请避免复制。
		if line == nil && !more {
			return l, nil
		}
		line = append(line, l...)
		if !more {
			break
		}
	}
	return line, nil
}

// ReadContinuedLine从r中读取一个可能连续的行，
// 省略最后的ASCII空格。
// 如果第一行后面的行以空格或制表符开头，则它们被视为连续体。在返回的数据中，连续行与前一行仅用一个空格隔开：换行符和前导空格被删除。ABCFDG 
// 第2行
// 
// 第一次调用ReadContinuedLine将返回“第1行继续…”
// 第二个将返回“第2行”。
// 
// 空行永远不会继续。
// 
func (r *Reader) ReadContinuedLine() (string, error) {
	line, err := r.readContinuedLineSlice(noValidation)
	return string(line), err
}

// 删除前导空格和尾随空格以及制表符的修剪返回s。
// 它不采用Unicode或UTF-8。
func trim(s []byte) []byte {
	i := 0
	for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
		i++
	}
	n := len(s)
	for n > i && (s[n-1] == ' ' || s[n-1] == '\t') {
		n--
	}
	return s[i:n]
}

// ReadContinuedLineBytes类似于ReadContinuedLine，但
// 返回[]字节而不是字符串。
func (r *Reader) ReadContinuedLineBytes() ([]byte, error) {
	line, err := r.readContinuedLineSlice(noValidation)
	if line != nil {
		buf := make([]byte, len(line))
		copy(buf, line)
		line = buf
	}
	return line, err
}

// readContinuedLineSlice从读卡器缓冲区读取连续的行，
// 返回包含所有行的字节片。validateFirstLine函数
// 在第一个读取行上运行，如果返回错误，则从readContinuedLineSlice返回此
// 错误。读第一行。
func (r *Reader) readContinuedLineSlice(validateFirstLine func([]byte) error) ([]byte, error) {
	if validateFirstLine == nil {
		return nil, fmt.Errorf("missing validateFirstLine func")
	}

	line, err := r.readLineSlice()
	if err != nil {
		return nil, err
	}
	if len(line) == 0 { // 空行-无延续
		return line, nil
	}

	if err := validateFirstLine(line); err != nil {
		return nil, err
	}

	// 乐观地假设我们已经开始缓冲下一行
	// 并且它以一个ASCII字母（下一个头键）或一个空白
	// 行开始，因此我们可以避免在内存
	// 中复制缓冲数据，并跳过不存在的空格。
	if r.R.Buffered() > 1 {
		peek, _ := r.R.Peek(2)
		if len(peek) > 0 && (isASCIILetter(peek[0]) || peek[0] == '\n') ||
			len(peek) == 2 && peek[0] == '\r' && peek[1] == '\n' {
			return trim(line), nil
		}
	}

	// ReadByte或下一个readLineSlice将刷新读取缓冲区；
	// 将切片复制到buf中。
	r.buf = append(r.buf[:0], trim(line)...)

	// 阅读续行。
	for r.skipSpace() > 0 {
		line, err := r.readLineSlice()
		if err != nil {
			break
		}
		r.buf = append(r.buf, ' ')
		r.buf = append(r.buf, trim(line)...)
	}
	return r.buf, nil
}

// skipSpace跳过所有空格的R并返回跳过的字节数。
func (r *Reader) skipSpace() int {
	n := 0
	for {
		c, err := r.R.ReadByte()
		if err != nil {
			// Bufio将保留错误，直到下次读取。
			break
		}
		if c != ' ' && c != '\t' {
			r.R.UnreadByte()
			break
		}
		n++
	}
	return n
}

func (r *Reader) readCodeLine(expectCode int) (code int, continued bool, message string, err error) {
	line, err := r.ReadLine()
	if err != nil {
		return
	}
	return parseCodeLine(line, expectCode)
}

func parseCodeLine(line string, expectCode int) (code int, continued bool, message string, err error) {
	if len(line) < 4 || line[3] != ' ' && line[3] != '-' {
		err = ProtocolError("short response: " + line)
		return
	}
	continued = line[3] == '-'
	code, err = strconv.Atoi(line[0:3])
	if err != nil || code < 100 {
		err = ProtocolError("invalid response code: " + line)
		return
	}
	message = line[4:]
	if 1 <= expectCode && expectCode < 10 && code/100 != expectCode ||
		10 <= expectCode && expectCode < 100 && code/10 != expectCode ||
		100 <= expectCode && expectCode < 1000 && code != expectCode {
		err = &Error{code, message}
	}
	return
}

// ReadCodeLine读取一个响应代码行，其格式为
// 代码消息
// 其中代码为三位状态代码，消息
// 扩展到该行的其余部分。例如：
// 220 plan9。贝尔实验室。com ESMTP 
// 
// 如果状态前缀与expectCode中的数字不匹配，
// ReadCodeLine返回，err设置为&Error{code，message}。
// 例如，如果expectCode为31，则如果
// 状态不在范围[310319]内，将返回一个错误。
// 
// 如果响应是多行的，ReadCodeLine将返回一个错误。
// 
// 如果expectCode<=0，则禁用对状态代码的检查。
// 
func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err error) {
	code, continued, message, err := r.readCodeLine(expectCode)
	if err == nil && continued {
		err = ProtocolError("unexpected multi-line response: " + message)
	}
	return
}

// ReadResponse读取多行响应，格式为：
// 
// 代码消息行1 
// 代码消息行2 
// /。。。
// 代码消息行n 
// 
// 其中代码是三位状态码。第一行以
// 代码和连字符开头。响应以一行结尾，该行以
// 开头，代码相同，后跟一个空格。消息中的每一行都是
// 由换行符（\n）分隔。
// 
// 参见RFC 959第36页(https:
// 接受的另一种回复形式的详细信息：
// 
// 代码消息行1 
// 消息行2 
//   ... 
// 代码消息行n 
// 
// 如果状态前缀与expectCode中的数字不匹配，
// ReadResponse返回，err设置为&Error{code，message}。
// 例如，如果expectCode为31，则如果
// 状态不在范围[310319]内，将返回一个错误。
// 
// 如果expectCode<=0，则禁用对状态代码的检查。
// 
func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) {
	code, continued, message, err := r.readCodeLine(expectCode)
	multi := continued
	for continued {
		line, err := r.ReadLine()
		if err != nil {
			return 0, "", err
		}

		var code2 int
		var moreMessage string
		code2, continued, moreMessage, err = parseCodeLine(line, 0)
		if err != nil || code2 != code {
			message += "\n" + strings.TrimRight(line, "\r\n")
			continued = true
			continue
		}
		message += "\n" + moreMessage
	}
	if err != nil && multi && message != "" {
		// 用所有行替换一行错误消息（完整消息）
		err = &Error{code, message}
	}
	return
}

// DotReader返回一个新的读卡器，该读卡器使用
// 从r读取的点编码块的解码文本。
// 返回的读卡器仅在下次调用
// 到r上的方法之前有效。
// 
// 点编码是文本协议（如SMTP）中用于数据块
// 的常用帧。数据由一个序列
// 行组成，每个行以“\r\n”结尾。序列本身
// 在一行结束，该行只包含一个点：“.\r\n”。以
// 开头并带有一个点的行将被另一个点转义，以避免
// 看起来像序列的结尾。
// 
// 读取器的读取方法返回的解码表单
// 将“\r\n”行结尾重写为更简单的“\n”，
// 删除前导点转义（如果存在），并以错误io停止。EOF 
// 消耗（并丢弃）序列行末尾后。
func (r *Reader) DotReader() io.Reader {
	r.closeDot()
	r.dot = &dotReader{r: r}
	return r.dot
}

type dotReader struct {
	r     *Reader
	state int
}

// 读取通过解码从d.r.读取的点编码数据来满足读取。
func (d *dotReader) Read(b []byte) (n int, err error) {
	// 通过简单的状态机运行数据到
	// 删除前导点，将尾随\r\n重写为\n，
	// 并检测结束。\r\n行。
	const (
		stateBeginLine = iota // 行首；初始状态；必须为零
		stateDot              // 读取。在第
		stateDotCR            // 行的开头，请阅读。\r在第
		stateCR               // 行开始处读取\r（可能在行结束处）
		stateData             // 读取第
		stateEOF              // 行中间的数据。\r\n结束标记线
	)
	br := d.r.R
	for n < len(b) && d.state != stateEOF {
		var c byte
		c, err = br.ReadByte()
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			break
		}
		switch d.state {
		case stateBeginLine:
			if c == '.' {
				d.state = stateDot
				continue
			}
			if c == '\r' {
				d.state = stateCR
				continue
			}
			d.state = stateData

		case stateDot:
			if c == '\r' {
				d.state = stateDotCR
				continue
			}
			if c == '\n' {
				d.state = stateEOF
				continue
			}
			d.state = stateData

		case stateDotCR:
			if c == '\n' {
				d.state = stateEOF
				continue
			}
			// 不属于。\r\n.
			// 使用前导点并发出已保存的\r.
			br.UnreadByte()
			c = '\r'
			d.state = stateData

		case stateCR:
			if c == '\n' {
				d.state = stateBeginLine
				break
			}
			// 不属于\r\n.emit saved\r 
			br.UnreadByte()
			c = '\r'
			d.state = stateData

		case stateData:
			if c == '\r' {
				d.state = stateCR
				continue
			}
			if c == '\n' {
				d.state = stateBeginLine
			}
		}
		b[n] = c
		n++
	}
	if err == nil && d.state == stateEOF {
		err = io.EOF
	}
	if err != nil && d.r.dot == d {
		d.r.dot = nil
	}
	return
}

// closeDot耗尽当前的点读取器（如果有），
// 确保它一直读取到结束点行。
func (r *Reader) closeDot() {
	if r.dot == nil {
		return
	}
	buf := make([]byte, 128)
	for r.dot != nil {
		// 当读取达到EOF或出现错误时，
		// 它将设置r.dot==nil。
		r.dot.Read(buf)
	}
}

// ReadDotBytes读取点编码并返回解码数据。
// 
// 有关点编码的详细信息，请参阅DotReader方法的文档。
func (r *Reader) ReadDotBytes() ([]byte, error) {
	return io.ReadAll(r.DotReader())
}

// ReadDotLines读取点编码并返回一个片段
// 包含解码的行，最后的\r\n或\n从每个行中删除。
// 
// 有关点编码的详细信息，请参阅DotReader方法的文档。
func (r *Reader) ReadDotLines() ([]string, error) {
	// 我们可以使用ReadDotBytes，然后将其拆分，
	// 但是一次读取一行可以避免需要大量连续内存块，而且更简单。
	var v []string
	var err error
	for {
		var line string
		line, err = r.ReadLine()
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			break
		}

		// 点本身标志着结束；否则切一个点。
		if len(line) > 0 && line[0] == '.' {
			if len(line) == 1 {
				break
			}
			line = line[1:]
		}
		v = append(v, line)
	}
	return v, err
}

var colon = []byte(":")

// ReadMIMEHeader从r中读取MIME样式的头。
// 头是一系列可能连续的键：值行
// 以空行结尾。
// 返回的映射m将CanonicalTimeHeaderKey（key）映射到
// 输入中遇到的相同顺序的值序列。例如，ABCFDG 
// 
// map[string][]string{
// “我的键”：{“值1”，“值2”}，
// “长键”：{“更长的值”}，
// /}
// /
func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) {
	// 通过提前分配一个
	// 大的一个来避免以后大量的小切片分配，我们将其切割成更小的
	// 切片。如果这个以后不够大，我们就分配小的。
	var strs []string
	hint := r.upcomingHeaderNewlines()
	if hint > 0 {
		strs = make([]string, hint)
	}

	m := make(MIMEHeader, hint)

	// 第一行不能以前导空格开头。
	if buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\t') {
		line, err := r.readLineSlice()
		if err != nil {
			return m, err
		}
		return m, ProtocolError("malformed MIME header initial line: " + string(line))
	}

	for {
		kv, err := r.readContinuedLineSlice(mustHaveFieldNameColon)
		if len(kv) == 0 {
			return m, err
		}

		// 键在第一个冒号处结束。
		k, v, ok := bytes.Cut(kv, colon)
		if !ok {
			return m, ProtocolError("malformed MIME header line: " + string(kv))
		}
		key := canonicalMIMEHeaderKey(k)

		// 根据RFC 7230，字段名是一个令牌，令牌由一个或多个字符组成。
		// 我们可以在这里返回一个协议错误，但最好是在我们接受的内容上自由，所以如果我们得到一个空密钥，跳过它。
		if key == "" {
			continue
		}

		// 跳过值中的初始空格。
		value := strings.TrimLeft(string(v), " \t")

		vv := m[key]
		if vv == nil && len(strs) > 0 {
			// 这很可能是一个单元素键。
			// 大多数头不是多值的。
			// 将strs[0]上的容量设置为1，这样以后的任何追加
			// 都不会将切片扩展到其他字符串中。
			vv, strs = strs[:1:1], strs[1:]
			vv[0] = value
			m[key] = vv
		} else {
			m[key] = append(vv, value)
		}

		if err != nil {
			return m, err
		}
	}
}

// noValidation是readContinuedLineSlice的无操作验证函数
// 允许任何行。
func noValidation(_ []byte) error { return nil }

// musthavefieldname冒号确保根据RFC 7230，
// 字段名位于一行，因此第一行必须包含冒号。
func mustHaveFieldNameColon(line []byte) error {
	if bytes.IndexByte(line, ':') < 0 {
		return ProtocolError(fmt.Sprintf("malformed MIME header: missing colon: %q", line))
	}
	return nil
}

var nl = []byte("\n")

// upcomingHeaderNewlines返回此标题中新行数
// 的近似值。如果它感到困惑，它将返回0。
func (r *Reader) upcomingHeaderNewlines() (n int) {
	// 尝试确定“提示”大小。
	r.R.Peek(1) // 如果为空，则强制加载缓冲区
	s := r.R.Buffered()
	if s == 0 {
		return
	}
	peek, _ := r.R.Peek(s)
	return bytes.Count(peek, nl)
}

// CanonicalTimeHeaderKey返回
// MIME头键的规范格式。规范化将第一个
// 字母和连字符后面的任何字母转换为大写；
// 其余部分转换为小写。例如，
// 用于“接受编码”的标准密钥是“接受编码”。
// MIME头键被假定为仅ASCII码。
// 如果s包含空格或无效的头字段字节，则返回
// 而不进行修改。
func CanonicalMIMEHeaderKey(s string) string {
	commonHeaderOnce.Do(initCommonHeader)

	// 快速检查规范编码。
	upper := true
	for i := 0; i < len(s); i++ {
		c := s[i]
		if !validHeaderFieldByte(c) {
			return s
		}
		if upper && 'a' <= c && c <= 'z' {
			return canonicalMIMEHeaderKey([]byte(s))
		}
		if !upper && 'A' <= c && c <= 'Z' {
			return canonicalMIMEHeaderKey([]byte(s))
		}
		upper = c == '-'
	}
	return s
}

const toLower = 'a' - 'A'

// validHeaderFieldByte报告b是否是头中的有效字节
// 字段名。RFC 7230显示：
// 头字段=字段名“：“OWS字段值OWS 
// 字段名=令牌
// tchar=“！”/"#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / 
// “^”/“”/“`”/“|”/“~”/DIGIT/ALPHA 
// token=1*tchar 
func validHeaderFieldByte(b byte) bool {
	return int(b) < len(isTokenTable) && isTokenTable[b]
}

// CanonicalTimeHeaderKey类似于CanonicalTimeHeaderKey，但在返回
// 字符串之前，允许对提供的字节片进行变异。
// 
// 对于无效输入（如果a包含空格或非令牌字节），
// 将保持不变，并返回字符串副本。
func canonicalMIMEHeaderKey(a []byte) string {
	// 查看a是否看起来像头键。如果没有，请原封不动地返回。
	for _, c := range a {
		if validHeaderFieldByte(c) {
			continue
		}
		// 不要规范化。
		return string(a)
	}

	upper := true
	for i, c := range a {
		// 规范化：第一个字母大写
		// 以及每个破折号后的大写字母。
		// /（主机、用户代理，如果自修改后）。
		// MIME头仅为ASCII，因此没有Unicode问题。
		if upper && 'a' <= c && c <= 'z' {
			c -= toLower
		} else if !upper && 'A' <= c && c <= 'Z' {
			c += toLower
		}
		a[i] = c
		upper = c == '-' // 对于下一次
	}
	// 编译器将m[string（byteSlice）]识别为一个特殊的
	// 大小写，因此将a的字节复制到一个新字符串中不会在这个映射查找中发生：
	if v := commonHeader[string(a)]; v != "" {
		return v
	}
	return string(a)
}

// commonHeader实习生common header string。
var commonHeader map[string]string

var commonHeaderOnce sync.Once

func initCommonHeader() {
	commonHeader = make(map[string]string)
	for _, v := range []string{
		"Accept",
		"Accept-Charset",
		"Accept-Encoding",
		"Accept-Language",
		"Accept-Ranges",
		"Cache-Control",
		"Cc",
		"Connection",
		"Content-Id",
		"Content-Language",
		"Content-Length",
		"Content-Transfer-Encoding",
		"Content-Type",
		"Cookie",
		"Date",
		"Dkim-Signature",
		"Etag",
		"Expires",
		"From",
		"Host",
		"If-Modified-Since",
		"If-None-Match",
		"In-Reply-To",
		"Last-Modified",
		"Location",
		"Message-Id",
		"Mime-Version",
		"Pragma",
		"Received",
		"Return-Path",
		"Server",
		"Set-Cookie",
		"Subject",
		"To",
		"User-Agent",
		"Via",
		"X-Forwarded-For",
		"X-Imforwards",
		"X-Powered-By",
	} {
		commonHeader[v] = v
	}
}

// isTokenTable是net/http/lex的副本。去吧，我可以去。
// 参见https:
var isTokenTable = [127]bool{
	'!':  true,
	'#':  true,
	'$':  true,
	'%':  true,
	'&':  true,
	'\'': true,
	'*':  true,
	'+':  true,
	'-':  true,
	'.':  true,
	'0':  true,
	'1':  true,
	'2':  true,
	'3':  true,
	'4':  true,
	'5':  true,
	'6':  true,
	'7':  true,
	'8':  true,
	'9':  true,
	'A':  true,
	'B':  true,
	'C':  true,
	'D':  true,
	'E':  true,
	'F':  true,
	'G':  true,
	'H':  true,
	'I':  true,
	'J':  true,
	'K':  true,
	'L':  true,
	'M':  true,
	'N':  true,
	'O':  true,
	'P':  true,
	'Q':  true,
	'R':  true,
	'S':  true,
	'T':  true,
	'U':  true,
	'W':  true,
	'V':  true,
	'X':  true,
	'Y':  true,
	'Z':  true,
	'^':  true,
	'_':  true,
	'`':  true,
	'a':  true,
	'b':  true,
	'c':  true,
	'd':  true,
	'e':  true,
	'f':  true,
	'g':  true,
	'h':  true,
	'i':  true,
	'j':  true,
	'k':  true,
	'l':  true,
	'm':  true,
	'n':  true,
	'o':  true,
	'p':  true,
	'q':  true,
	'r':  true,
	's':  true,
	't':  true,
	'u':  true,
	'v':  true,
	'w':  true,
	'x':  true,
	'y':  true,
	'z':  true,
	'|':  true,
	'~':  true,
}
